package Derivation;


import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;

import TopK.EquationTopK;
import Basics.*;

public class DeriveByProgramBottomUp extends DeriveByProgram 
{	
	
	Map<Atom, HashSet<Body>> provenance;
	
	//HashSet<Rule> constRules = new HashSet<Rule>();
	
	EquationTopK topk;
	
	Set<Atom> addedInstAtomsInLastIteration;
	
	Map<String, Atom> keyMap;
	
	Set<Atom> atomsAddedSoFar = new HashSet<Atom>();
	
	Set<Atom> addedInstAtomsInThisIteration = new HashSet<Atom>();
	
	int level = 0;
	
	int beginIdx = 0;
	
	
	
	public DeriveByProgramBottomUp (Map<Atom, HashSet<Body>> iprov, Map<String, Atom> imap, Rule ... irs)
	{
		super(irs);
		this.provenance = iprov;
		this.keyMap = imap;
	}
	
	
	
	public DeriveByProgramBottomUp (Map<Atom, HashSet<Body>> iprov, Map<String, Atom> imap, Program p)
	{
		super(p);
		this.provenance = iprov;
		this.keyMap = imap;
	}
	
	
	
	public DeriveByProgramBottomUp (Map<Atom, HashSet<Body>> iprov, Map<String, Atom> imap, Program p, EquationTopK itopk)
	{
		super(p);
		this.provenance = iprov;
		this.keyMap = imap;
		this.topk = itopk;
	}
	

	
	public Set<Atom> getAtomsAddedSoFar() 
	{
		return atomsAddedSoFar;
	}

	
	
	public Set<Atom> getAddedInstAtomsInLastIteration() 
	{
		return addedInstAtomsInThisIteration;
	}
	
	

	public Map<Atom, HashSet<Body>> getProvenanceProgram() 
	{
		return provenance;
	}



	public Set<Atom> getAddedInstAtomsInThisIteration() 
	{
		return addedInstAtomsInThisIteration;
	}



	public Map<Atom, HashSet<Body>> getProvenance() 
	{
		return provenance;
	}



	public void setProvenance(Map<Atom, HashSet<Body>> provenance) 
	{
		this.provenance = provenance;
	}



	public Map<String, Atom> getKeyMap() 
	{
		return keyMap;
	}



	public void setKeyMap(Map<String, Atom> keyMap) 
	{
		this.keyMap = keyMap;
	}



	/*************************************************************************************************************/
	/** Title: DeriveFromProgramIterationBottomUp																				
	/** Description: Finds all Derivations for the rule according to db 				
	/*************************************************************************************************************/
	
	public void DeriveFromProgramIterationBottomUp (boolean forIntersection, boolean online)
	{
		level++;
		Reset();
		//this.provenanceProgram.clear();
		//long startTime = System.currentTimeMillis();
		this.addedInstAtomsInLastIteration = new HashSet<Atom>(this.addedInstAtomsInThisIteration);
		this.addedInstAtomsInThisIteration.clear();
		for (int i = beginIdx; i < this.p.getRules().size(); i++)
		{
			Rule rule = this.p.getRules().elementAt(i);
			if (false == forIntersection) 
			{
				if (false == IsBodyEDB(rule) || true == this.addedInstAtomsInThisIteration.isEmpty()) //if body of rule relies on derived facts.
				{
					InstantiateAllRelevantsInRule(rule, forIntersection, online);
				}
			}
			
			else if (rule.getDerivedInlevel() == level)
			{
				InstantiateAllRelevantsInRule(rule, forIntersection, online);
			}
			
			else
			{
				beginIdx = i;
				break;
			}
		}
		/*long end = System.currentTimeMillis();
		if ((end-startTime) > 1000) 
		{
			System.out.println("DeriveFromProgramIterationBottomUp:: time for iteration over all rules: " + (end-startTime));
			System.out.println("DeriveFromProgramIterationBottomUp:: numRulesUsed = " + numRulesUsed);
		}*/

		//this.addedInstAtomsInThisIteration.clear();
		//UpdateAtomsAdded(false, forIntersection);
		//UpdateAtomsAdded(true, forIntersection);
	}
	
	
	
	/*************************************************************************************************************/
	/** Title: IsBodyEDB																				
	/** Description: Check if rule can derive a fact only with DB atoms  				
	/*************************************************************************************************************/
	
	public boolean IsBodyEDB (Rule rule)
	{
		boolean retVal = true;
		for (Atom bodyAtom : rule.getBody().getAtoms()) 
		{
			if (false == bodyAtom.IsAtomRelationEdb(this.p)) 
			{
				retVal = false;
				break;
			}
		}
		
		return retVal;
	}
	
	
	
	/*************************************************************************************************************/
	/** Title: UpdateAddedInstAtoms																				
	/** Description:  				
	/*************************************************************************************************************/

	public void UpdateAtomsAdded (boolean isAtomsAddedSoFar, boolean forIntersection)
	{
		//long startTime = System.currentTimeMillis();
		for (Atom atom : this.provenance.keySet())
		{
			if (true == isAtomsAddedSoFar) 
			{
				this.atomsAddedSoFar.add(atom);
			}
			
			else
			{
				/*if (false == this.addedInstAtomsInThisIteration.contains(atom)) 
				{
					this.addedInstAtomsInThisIteration.add(atom);				
				}*/
				
				if (false == this.keyMap.containsKey(atom.toString())) 
				{
					this.keyMap.put(atom.toString(), atom);
				}
			}
		}
		/*long endTime = System.currentTimeMillis();
		if ((endTime-startTime) > 1000) {
			System.out.println("iteration time: "  + (endTime-startTime));
			System.out.println("size of atomsAddedSoFar after adding: " + atomsAddedSoFar.size());
		}*/
	}
	
	
	/*************************************************************************************************************/
	/** Title: InstantiateAllRelevantsInRule																				
	/** Description: 				
	/*************************************************************************************************************/
	
	public void InstantiateAllRelevantsInRule (Rule rule, boolean forIntersection, boolean online)
	{
		List<Rule> instRuleVector = new CopyOnWriteArrayList<Rule>();
		
		//long startTime = System.currentTimeMillis();
		if (false == forIntersection && false == rule.isFullyInst()) 
		{
			for (Atom bodyAtom : rule.getBody().getAtoms()) 
			{
				for (Atom instAtom : this.addedInstAtomsInLastIteration) 
				{
					if (instAtom.getName().equals(bodyAtom.getName()))
					{
						Atom copyOfinstAtom = new Atom (instAtom);
						Rule temp = new Rule (rule);
						temp.SwapToInstAtomAndPartiallyInst(bodyAtom, copyOfinstAtom);
						if (temp.isFullyInst())
						{
							temp.getBody().setRuleUsed(rule);//added for the top-k
							AddToProv(temp);
						}

						else if (false == instRuleVector.contains(temp))
						{
							instRuleVector.add(temp);
						}
					}
				}
			}
			
			if (false == instRuleVector.isEmpty() || true == this.addedInstAtomsInLastIteration.isEmpty())
			{
				DeriveByRuleBottomUp ruleDeriver = new DeriveByRuleBottomUp(rule, this.p, this.provenance, this.keyMap, this.addedInstAtomsInThisIteration, this.topk);
				ruleDeriver.FindDerivationsForRuleBottomUp(instRuleVector, forIntersection, level);	
			}
		}
		
		else
		{
			if (false == online ||  true == rule.getBody().AllAtomsInDb()) 
			{
				rule.getBody().setRuleUsed(rule);//added for the top-k
				AddToProv(rule);
			}
			
		}
		
		/*long endTime = System.currentTimeMillis();
		if ((endTime-startTime) > 200)
		{
			System.out.println("DeriveByProgramBottomUp::InstantiateAllRelevantsInRule:: iteration time: "  + (endTime-startTime));
			System.out.println("DeriveByProgramBottomUp::InstantiateAllRelevantsInRule:: rule derived: "  + rule);
		}*/
	}
	
	
	
	/*************************************************************************************************************/
	/** Title: AddToProv																				
	/** Description: 				
	/*************************************************************************************************************/
	
	public void AddToProv (Rule rule)
	{
		if (false == this.keyMap.containsKey(rule.getHead().toString()))
		{
			this.provenance.put(rule.getHead(), new HashSet<Body>());
			this.keyMap.put(rule.getHead().toString(), rule.getHead());
		}
		
		if (false == rule.getHead().TreesContainBody(rule.getBody()))
		{
			Body pointerBody = new Body();
			pointerBody.setDerivedInlevel(this.level);
			for (Atom atom : rule.getBody().getAtoms()) 
			{
				if (true == atom.isFact() && false == this.keyMap.containsKey(atom.toString()))
				{
					this.provenance.put(atom, new HashSet<Body>());
					this.keyMap.put(atom.toString(), atom);
					this.topk.SetTreeForFact(atom);
				}
				
				pointerBody.getAtoms().add(this.keyMap.get(atom.toString()));
			}
			
			pointerBody.setRuleUsed(rule.getBody().getRuleUsed());
			
			this.provenance.get(rule.getHead()).add(pointerBody);
			
			this.addedInstAtomsInThisIteration.add(this.keyMap.get(rule.getHead().toString()));
			
			this.topk.UpdateTreesWhileSemiNaive(this.keyMap.get(rule.getHead().toString()), pointerBody);
		}
	}	
	
	
	

	/*************************************************************************************************************/
	/** Title: Reset																				
	/** Description: resets the parameters for next step of the program			
	/*************************************************************************************************************/
	
	public void Reset ()
	{
		//this.addedInstAtomsInLastIteration.clear();
	}
}
